
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

/**
 * AUTO-GENERATED FILE. DO NOT MODIFY.
 */

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
// 
//   http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.
import { __extends } from 'tslib';
// A third-party license is embedded for some of the code in this file:
// The "scaleLevels" was originally copied from "d3.js" with some
// modifications made for this project.
// (See more details in the comment on the definition of "scaleLevels" below.)
// The use of the source code of this file is also subject to the terms
// and consitions of the license of "d3.js" (BSD-3Clause, see
// </licenses/LICENSE-d3>).
// [About UTC and local time zone]:
// In most cases, `number.parseDate` will treat input data string as local time
// (except time zone is specified in time string). And `format.formateTime` returns
// local time by default. option.useUTC is false by default. This design has
// considered these common cases:
// (1) Time that is persistent in server is in UTC, but it is needed to be displayed
// in local time by default.
// (2) By default, the input data string (e.g., '2011-01-02') should be displayed
// as its original time, without any time difference.

import * as numberUtil from '../util/number.js';
import { ONE_SECOND, ONE_MINUTE, ONE_HOUR, ONE_DAY, ONE_YEAR, format, leveledFormat, getUnitValue, timeUnits, fullLeveledFormatter, getPrimaryTimeUnit, isPrimaryTimeUnit, getDefaultFormatPrecisionOfInterval, fullYearGetterName, monthSetterName, fullYearSetterName, dateSetterName, hoursGetterName, hoursSetterName, minutesSetterName, secondsSetterName, millisecondsSetterName, monthGetterName, dateGetterName, minutesGetterName, secondsGetterName, millisecondsGetterName } from '../util/time.js';
import * as scaleHelper from './helper.js';
import IntervalScale from './Interval.js';
import Scale from './Scale.js';
import { warn } from '../util/log.js';
import { filter, isNumber, map } from 'zrender/lib/core/util.js'; // FIXME 公用？

var bisect = function (a, x, lo, hi) {
	while (lo < hi) {
		var mid = lo + hi >>> 1;

		if (a[mid][1] < x) {
			lo = mid + 1;
		} else {
			hi = mid;
		}
	}

	return lo;
};

var TimeScale =
/** @class */
function (_super) {
	__extends(TimeScale, _super);

	function TimeScale(settings) {
		var _this = _super.call(this, settings) || this;

		_this.type = 'time';
		return _this;
	}
	/**
   * Get label is mainly for other components like dataZoom, tooltip.
   */

	TimeScale.prototype.getLabel = function (tick) {
		var useUTC = this.getSetting('useUTC');
		return format(tick.value, fullLeveledFormatter[getDefaultFormatPrecisionOfInterval(getPrimaryTimeUnit(this._minLevelUnit))] || fullLeveledFormatter.second, useUTC, this.getSetting('locale'));
	};

	TimeScale.prototype.getFormattedLabel = function (tick, idx, labelFormatter) {
		var isUTC = this.getSetting('useUTC');
		var lang = this.getSetting('locale');
		return leveledFormat(tick, idx, labelFormatter, lang, isUTC);
	};
	/**
   * @override
   */

	TimeScale.prototype.getTicks = function () {
		var interval = this._interval;
		var extent = this._extent;
		var ticks = []; // If interval is 0, return [];

		if (!interval) {
			return ticks;
		}

		ticks.push({
			value: extent[0],
			level: 0
		});
		var useUTC = this.getSetting('useUTC');
		var innerTicks = getIntervalTicks(this._minLevelUnit, this._approxInterval, useUTC, extent);
		ticks = ticks.concat(innerTicks);
		ticks.push({
			value: extent[1],
			level: 0
		});
		return ticks;
	};

	TimeScale.prototype.calcNiceExtent = function (opt) {
		var extent = this._extent; // If extent start and end are same, expand them

		if (extent[0] === extent[1]) {
			// Expand extent
			extent[0] -= ONE_DAY;
			extent[1] += ONE_DAY;
		} // If there are no data and extent are [Infinity, -Infinity]

		if (extent[1] === -Infinity && extent[0] === Infinity) {
			var d = new Date();
			extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate());
			extent[0] = extent[1] - ONE_DAY;
		}

		this.calcNiceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval);
	};

	TimeScale.prototype.calcNiceTicks = function (approxTickNum, minInterval, maxInterval) {
		approxTickNum = approxTickNum || 10;
		var extent = this._extent;
		var span = extent[1] - extent[0];
		this._approxInterval = span / approxTickNum;

		if (minInterval != null && this._approxInterval < minInterval) {
			this._approxInterval = minInterval;
		}

		if (maxInterval != null && this._approxInterval > maxInterval) {
			this._approxInterval = maxInterval;
		}

		var scaleIntervalsLen = scaleIntervals.length;
		var idx = Math.min(bisect(scaleIntervals, this._approxInterval, 0, scaleIntervalsLen), scaleIntervalsLen - 1); // Interval that can be used to calculate ticks

		this._interval = scaleIntervals[idx][1]; // Min level used when picking ticks from top down.
		// We check one more level to avoid the ticks are to sparse in some case.

		this._minLevelUnit = scaleIntervals[Math.max(idx - 1, 0)][0];
	};

	TimeScale.prototype.parse = function (val) {
		// val might be float.
		return isNumber(val) ? val : +numberUtil.parseDate(val);
	};

	TimeScale.prototype.contain = function (val) {
		return scaleHelper.contain(this.parse(val), this._extent);
	};

	TimeScale.prototype.normalize = function (val) {
		return scaleHelper.normalize(this.parse(val), this._extent);
	};

	TimeScale.prototype.scale = function (val) {
		return scaleHelper.scale(val, this._extent);
	};

	TimeScale.type = 'time';
	return TimeScale;
}(IntervalScale);
/**
 * This implementation was originally copied from "d3.js"
 * <https://github.com/d3/d3/blob/b516d77fb8566b576088e73410437494717ada26/src/time/scale.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 */

var scaleIntervals = [// Format                           interval
	['second', ONE_SECOND], ['minute', ONE_MINUTE], ['hour', ONE_HOUR], ['quarter-day', ONE_HOUR * 6], ['half-day', ONE_HOUR * 12], ['day', ONE_DAY * 1.2], ['half-week', ONE_DAY * 3.5], ['week', ONE_DAY * 7], ['month', ONE_DAY * 31], ['quarter', ONE_DAY * 95], ['half-year', ONE_YEAR / 2], ['year', ONE_YEAR] // 1Y
];

function isUnitValueSame(unit, valueA, valueB, isUTC) {
	var dateA = numberUtil.parseDate(valueA);
	var dateB = numberUtil.parseDate(valueB);

	var isSame = function (unit) {
		return getUnitValue(dateA, unit, isUTC) === getUnitValue(dateB, unit, isUTC);
	};

	var isSameYear = function () {
		return isSame('year');
	}; // const isSameHalfYear = () => isSameYear() && isSame('half-year');
	// const isSameQuater = () => isSameYear() && isSame('quarter');

	var isSameMonth = function () {
		return isSameYear() && isSame('month');
	};

	var isSameDay = function () {
		return isSameMonth() && isSame('day');
	}; // const isSameHalfDay = () => isSameDay() && isSame('half-day');

	var isSameHour = function () {
		return isSameDay() && isSame('hour');
	};

	var isSameMinute = function () {
		return isSameHour() && isSame('minute');
	};

	var isSameSecond = function () {
		return isSameMinute() && isSame('second');
	};

	var isSameMilliSecond = function () {
		return isSameSecond() && isSame('millisecond');
	};

	switch (unit) {
	case 'year':
		return isSameYear();

	case 'month':
		return isSameMonth();

	case 'day':
		return isSameDay();

	case 'hour':
		return isSameHour();

	case 'minute':
		return isSameMinute();

	case 'second':
		return isSameSecond();

	case 'millisecond':
		return isSameMilliSecond();
	}
} // const primaryUnitGetters = {
//     year: fullYearGetterName(),
//     month: monthGetterName(),
//     day: dateGetterName(),
//     hour: hoursGetterName(),
//     minute: minutesGetterName(),
//     second: secondsGetterName(),
//     millisecond: millisecondsGetterName()
// };
// const primaryUnitUTCGetters = {
//     year: fullYearGetterName(true),
//     month: monthGetterName(true),
//     day: dateGetterName(true),
//     hour: hoursGetterName(true),
//     minute: minutesGetterName(true),
//     second: secondsGetterName(true),
//     millisecond: millisecondsGetterName(true)
// };
// function moveTick(date: Date, unitName: TimeUnit, step: number, isUTC: boolean) {
//     step = step || 1;
//     switch (getPrimaryTimeUnit(unitName)) {
//         case 'year':
//             date[fullYearSetterName(isUTC)](date[fullYearGetterName(isUTC)]() + step);
//             break;
//         case 'month':
//             date[monthSetterName(isUTC)](date[monthGetterName(isUTC)]() + step);
//             break;
//         case 'day':
//             date[dateSetterName(isUTC)](date[dateGetterName(isUTC)]() + step);
//             break;
//         case 'hour':
//             date[hoursSetterName(isUTC)](date[hoursGetterName(isUTC)]() + step);
//             break;
//         case 'minute':
//             date[minutesSetterName(isUTC)](date[minutesGetterName(isUTC)]() + step);
//             break;
//         case 'second':
//             date[secondsSetterName(isUTC)](date[secondsGetterName(isUTC)]() + step);
//             break;
//         case 'millisecond':
//             date[millisecondsSetterName(isUTC)](date[millisecondsGetterName(isUTC)]() + step);
//             break;
//     }
//     return date.getTime();
// }
// const DATE_INTERVALS = [[8, 7.5], [4, 3.5], [2, 1.5]];
// const MONTH_INTERVALS = [[6, 5.5], [3, 2.5], [2, 1.5]];
// const MINUTES_SECONDS_INTERVALS = [[30, 30], [20, 20], [15, 15], [10, 10], [5, 5], [2, 2]];

function getDateInterval(approxInterval, daysInMonth) {
	approxInterval /= ONE_DAY;
	return approxInterval > 16 ? 16 // Math.floor(daysInMonth / 2) + 1  // In this case we only want one tick between two months.
		: approxInterval > 7.5 ? 7 // TODO week 7 or day 8?
			: approxInterval > 3.5 ? 4 : approxInterval > 1.5 ? 2 : 1;
}

function getMonthInterval(approxInterval) {
	var APPROX_ONE_MONTH = 30 * ONE_DAY;
	approxInterval /= APPROX_ONE_MONTH;
	return approxInterval > 6 ? 6 : approxInterval > 3 ? 3 : approxInterval > 2 ? 2 : 1;
}

function getHourInterval(approxInterval) {
	approxInterval /= ONE_HOUR;
	return approxInterval > 12 ? 12 : approxInterval > 6 ? 6 : approxInterval > 3.5 ? 4 : approxInterval > 2 ? 2 : 1;
}

function getMinutesAndSecondsInterval(approxInterval, isMinutes) {
	approxInterval /= isMinutes ? ONE_MINUTE : ONE_SECOND;
	return approxInterval > 30 ? 30 : approxInterval > 20 ? 20 : approxInterval > 15 ? 15 : approxInterval > 10 ? 10 : approxInterval > 5 ? 5 : approxInterval > 2 ? 2 : 1;
}

function getMillisecondsInterval(approxInterval) {
	return numberUtil.nice(approxInterval, true);
}

function getFirstTimestampOfUnit(date, unitName, isUTC) {
	var outDate = new Date(date);

	switch (getPrimaryTimeUnit(unitName)) {
	case 'year':
	case 'month':
		outDate[monthSetterName(isUTC)](0);

	case 'day':
		outDate[dateSetterName(isUTC)](1);

	case 'hour':
		outDate[hoursSetterName(isUTC)](0);

	case 'minute':
		outDate[minutesSetterName(isUTC)](0);

	case 'second':
		outDate[secondsSetterName(isUTC)](0);
		outDate[millisecondsSetterName(isUTC)](0);
	}

	return outDate.getTime();
}

function getIntervalTicks(bottomUnitName, approxInterval, isUTC, extent) {
	var safeLimit = 10000;
	var unitNames = timeUnits;
	var iter = 0;

	function addTicksInSpan(interval, minTimestamp, maxTimestamp, getMethodName, setMethodName, isDate, out) {
		var date = new Date(minTimestamp);
		var dateTime = minTimestamp;
		var d = date[getMethodName](); // if (isDate) {
		//     d -= 1; // Starts with 0;   PENDING
		// }

		while (dateTime < maxTimestamp && dateTime <= extent[1]) {
			out.push({
				value: dateTime
			});
			d += interval;
			date[setMethodName](d);
			dateTime = date.getTime();
		} // This extra tick is for calcuating ticks of next level. Will not been added to the final result

		out.push({
			value: dateTime,
			notAdd: true
		});
	}

	function addLevelTicks(unitName, lastLevelTicks, levelTicks) {
		var newAddedTicks = [];
		var isFirstLevel = !lastLevelTicks.length;

		if (isUnitValueSame(getPrimaryTimeUnit(unitName), extent[0], extent[1], isUTC)) {
			return;
		}

		if (isFirstLevel) {
			lastLevelTicks = [{
				// TODO Optimize. Not include so may ticks.
				value: getFirstTimestampOfUnit(new Date(extent[0]), unitName, isUTC)
			}, {
				value: extent[1]
			}];
		}

		for (var i = 0; i < lastLevelTicks.length - 1; i++) {
			var startTick = lastLevelTicks[i].value;
			var endTick = lastLevelTicks[i + 1].value;

			if (startTick === endTick) {
				continue;
			}

			var interval = void 0;
			var getterName = void 0;
			var setterName = void 0;
			var isDate = false;

			switch (unitName) {
			case 'year':
				interval = Math.max(1, Math.round(approxInterval / ONE_DAY / 365));
				getterName = fullYearGetterName(isUTC);
				setterName = fullYearSetterName(isUTC);
				break;

			case 'half-year':
			case 'quarter':
			case 'month':
				interval = getMonthInterval(approxInterval);
				getterName = monthGetterName(isUTC);
				setterName = monthSetterName(isUTC);
				break;

			case 'week': // PENDING If week is added. Ignore day.

			case 'half-week':
			case 'day':
				interval = getDateInterval(approxInterval, 31); // Use 32 days and let interval been 16

				getterName = dateGetterName(isUTC);
				setterName = dateSetterName(isUTC);
				isDate = true;
				break;

			case 'half-day':
			case 'quarter-day':
			case 'hour':
				interval = getHourInterval(approxInterval);
				getterName = hoursGetterName(isUTC);
				setterName = hoursSetterName(isUTC);
				break;

			case 'minute':
				interval = getMinutesAndSecondsInterval(approxInterval, true);
				getterName = minutesGetterName(isUTC);
				setterName = minutesSetterName(isUTC);
				break;

			case 'second':
				interval = getMinutesAndSecondsInterval(approxInterval, false);
				getterName = secondsGetterName(isUTC);
				setterName = secondsSetterName(isUTC);
				break;

			case 'millisecond':
				interval = getMillisecondsInterval(approxInterval);
				getterName = millisecondsGetterName(isUTC);
				setterName = millisecondsSetterName(isUTC);
				break;
			}

			addTicksInSpan(interval, startTick, endTick, getterName, setterName, isDate, newAddedTicks);

			if (unitName === 'year' && levelTicks.length > 1 && i === 0) {
				// Add nearest years to the left extent.
				levelTicks.unshift({
					value: levelTicks[0].value - interval
				});
			}
		}

		for (var i = 0; i < newAddedTicks.length; i++) {
			levelTicks.push(newAddedTicks[i]);
		} // newAddedTicks.length && console.log(unitName, newAddedTicks);

		return newAddedTicks;
	}

	var levelsTicks = [];
	var currentLevelTicks = [];
	var tickCount = 0;
	var lastLevelTickCount = 0;

	for (var i = 0; i < unitNames.length && iter++ < safeLimit; ++i) {
		var primaryTimeUnit = getPrimaryTimeUnit(unitNames[i]);

		if (!isPrimaryTimeUnit(unitNames[i])) {
			// TODO
			continue;
		}

		addLevelTicks(unitNames[i], levelsTicks[levelsTicks.length - 1] || [], currentLevelTicks);
		var nextPrimaryTimeUnit = unitNames[i + 1] ? getPrimaryTimeUnit(unitNames[i + 1]) : null;

		if (primaryTimeUnit !== nextPrimaryTimeUnit) {
			if (currentLevelTicks.length) {
				lastLevelTickCount = tickCount; // Remove the duplicate so the tick count can be precisely.

				currentLevelTicks.sort(function (a, b) {
					return a.value - b.value;
				});
				var levelTicksRemoveDuplicated = [];

				for (var i_1 = 0; i_1 < currentLevelTicks.length; ++i_1) {
					var tickValue = currentLevelTicks[i_1].value;

					if (i_1 === 0 || currentLevelTicks[i_1 - 1].value !== tickValue) {
						levelTicksRemoveDuplicated.push(currentLevelTicks[i_1]);

						if (tickValue >= extent[0] && tickValue <= extent[1]) {
							tickCount++;
						}
					}
				}

				var targetTickNum = (extent[1] - extent[0]) / approxInterval; // Added too much in this level and not too less in last level

				if (tickCount > targetTickNum * 1.5 && lastLevelTickCount > targetTickNum / 1.5) {
					break;
				} // Only treat primary time unit as one level.

				levelsTicks.push(levelTicksRemoveDuplicated);

				if (tickCount > targetTickNum || bottomUnitName === unitNames[i]) {
					break;
				}
			} // Reset if next unitName is primary

			currentLevelTicks = [];
		}
	}

	if (process.env.NODE_ENV !== 'production') {
		if (iter >= safeLimit) {
			warn('Exceed safe limit.');
		}
	}

	var levelsTicksInExtent = filter(map(levelsTicks, function (levelTicks) {
		return filter(levelTicks, function (tick) {
			return tick.value >= extent[0] && tick.value <= extent[1] && !tick.notAdd;
		});
	}), function (levelTicks) {
		return levelTicks.length > 0;
	});
	var ticks = [];
	var maxLevel = levelsTicksInExtent.length - 1;

	for (var i = 0; i < levelsTicksInExtent.length; ++i) {
		var levelTicks = levelsTicksInExtent[i];

		for (var k = 0; k < levelTicks.length; ++k) {
			ticks.push({
				value: levelTicks[k].value,
				level: maxLevel - i
			});
		}
	}

	ticks.sort(function (a, b) {
		return a.value - b.value;
	}); // Remove duplicates

	var result = [];

	for (var i = 0; i < ticks.length; ++i) {
		if (i === 0 || ticks[i].value !== ticks[i - 1].value) {
			result.push(ticks[i]);
		}
	}

	return result;
}

Scale.registerClass(TimeScale);
export default TimeScale;